package com.yc.cloud.admin.service.impl;

import cn.hutool.core.util.StrUtil;
import com.yc.cloud.admin.constants.SnapshotConstants;
import com.yc.cloud.admin.utils.ImageUtils;
import com.yc.cloud.admin.dto.SnapshotConfig;
import com.yc.cloud.admin.dto.response.SnapshotResultVO;
import com.yc.cloud.admin.enums.SnapshotStatusEnum;
import com.yc.cloud.admin.enums.TriggerSourceEnum;
import com.yc.cloud.admin.service.CameraSnapshotExecutor;
import com.yc.cloud.admin.service.FFmpegStreamService;
import com.yc.cloud.admin.service.SnapshotStrategyResolver;
import com.yc.cloud.admin.utils.FFmpegUtils;
import com.yc.cloud.admin.utils.StreamUrlBuilder;
import com.yc.cloud.entity.HwCamera;
import com.yc.cloud.entity.HwCameraSnapshotRecord;
import com.yc.cloud.mapper.HwCameraMapper;
import com.yc.cloud.mapper.HwCameraSnapshotRecordMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 摄像头抓图执行器实现
 * 使用 FFmpeg 从流截图
 * 
 * @author kinggu
 * @since 2025-10-10
 */
@Slf4j
@Service
public class CameraSnapshotExecutorImpl implements CameraSnapshotExecutor {
    
    @Resource
    private FFmpegUtils ffmpegUtils;
    
    @Resource
    private FFmpegStreamService ffmpegStreamService;
    
    @Resource
    private SnapshotStrategyResolver snapshotStrategyResolver;
    
    @Resource
    private HwCameraMapper cameraMapper;
    
    @Resource
    private HwCameraSnapshotRecordMapper snapshotRecordMapper;
    
    @Resource
    private com.yc.cloud.admin.service.SnapshotStorageService snapshotStorageService;
    
    @Resource
    private com.yc.cloud.common.oss.service.OssFileService ossFileService;
    
    /**
     * 抓图保存根目录
     */
    @Value("${custom.path.snapshotRootDir:snapshots}")
    private String snapshotRootDir;
    
    // 用于批量异步抓图的线程池
    private final ExecutorService executorService = Executors.newFixedThreadPool(10);
    
    @Override
    public SnapshotResultVO executeSnapshot(Long cameraId, String triggerSource) {
        long startTime = System.currentTimeMillis();
        log.info("开始执行摄像头[{}]抓图，触发源: {}", cameraId, triggerSource);
        
        // 1. 查询摄像头信息
        HwCamera camera = cameraMapper.selectById(cameraId);
        if (camera == null) {
            log.error("摄像头[{}]不存在", cameraId);
            return buildFailedResult(cameraId, triggerSource, "摄像头不存在", startTime);
        }
        
        // 2. 获取抓图配置
        SnapshotConfig config = snapshotStrategyResolver.getCurrentSnapshotConfig(cameraId);
        if (config == null || config.getStrategyId() == null) {
            log.warn("摄像头[{}]没有抓图策略", cameraId);
            return buildFailedResult(cameraId, triggerSource, "没有抓图策略", startTime);
        }
        
        // 3. 获取可用的流地址（优先使用 RTMP，其次使用 RTSP）
        String snapUrl = getAvailableStreamUrl(camera);
        if (StrUtil.isBlank(snapUrl)) {
            log.error("摄像头[{}]没有可用的流地址", cameraId);
            return buildFailedResult(cameraId, triggerSource, "没有可用的流地址", startTime);
        }
        
        // 4. 检查流是否可用（拉流检查）
        String streamId = extractStreamId(camera);
        if (StrUtil.isNotBlank(streamId)) {
            boolean streamAvailable = ffmpegStreamService.checkStreamAvailable(streamId);
            if (!streamAvailable) {
                log.error("摄像头[{}]流不可用，无法抓图: streamId={}", cameraId, streamId);
                return buildFailedResult(cameraId, triggerSource, "流不可用，无法抓图", startTime);
            }
            log.info("摄像头[{}]流可用，开始抓图: streamId={}", cameraId, streamId);
        }
        
        // 5. 生成输出文件路径
        String outputFilePath = generateOutputFilePath(cameraId, camera.getName());
        
        // 6. 使用 FFmpeg 从流截图
        String savedFilePath;
        long fileSize;
        try {
            savedFilePath = ffmpegUtils.captureFrameFromStream(snapUrl, outputFilePath);
            
            if (StrUtil.isBlank(savedFilePath)) {
                log.error("摄像头[{}]FFmpeg截图失败，未生成文件", cameraId);
                return buildFailedResult(cameraId, triggerSource, "FFmpeg截图失败", startTime);
            }
            
            File imageFile = new File(savedFilePath);
            if (!imageFile.exists()) {
                log.error("摄像头[{}]截图文件不存在: {}", cameraId, savedFilePath);
                return buildFailedResult(cameraId, triggerSource, "截图文件不存在", startTime);
            }
            
            fileSize = imageFile.length();
            log.info("摄像头[{}]FFmpeg截图成功: {}, 大小: {} bytes", cameraId, savedFilePath, fileSize);
            
        } catch (Exception e) {
            log.error("摄像头[{}]FFmpeg截图异常", cameraId, e);
            return buildFailedResult(cameraId, triggerSource, "FFmpeg截图异常: " + e.getMessage(), startTime);
        }
        
        // 7. 保存文件到 OSS 文件表
        com.yc.cloud.common.oss.entity.OssFile ossFile = ossFileService.saveOssFile(savedFilePath);
        if (ossFile == null) {
            log.error("摄像头[{}]保存OSS文件记录失败: {}", cameraId, savedFilePath);
            return buildFailedResult(cameraId, triggerSource, "保存OSS文件记录失败", startTime);
        }
        
        // 8. 生成缩略图
        String thumbnailFileId = null;
        try {
            String thumbnailPath = generateThumbnail(savedFilePath);
            if (StrUtil.isNotEmpty(thumbnailPath)) {
                com.yc.cloud.common.oss.entity.OssFile thumbnailOssFile = ossFileService.saveOssFile(thumbnailPath);
                if (thumbnailOssFile != null) {
                    thumbnailFileId = thumbnailOssFile.getFileId();
                    log.info("摄像头[{}]缩略图生成成功: {}", cameraId, thumbnailPath);
                } else {
                    log.warn("摄像头[{}]缩略图OSS保存失败: {}", cameraId, thumbnailPath);
                }
            }
        } catch (Exception e) {
            log.warn("摄像头[{}]缩略图生成失败，继续保存原图记录", cameraId, e);
        }
        
        // 9. 保存抓图记录
        HwCameraSnapshotRecord record = new HwCameraSnapshotRecord();
        record.setCameraId(cameraId);
        record.setStrategyId(config.getStrategyId());
        record.setFileId(ossFile.getFileId());
        record.setThumbnailFileId(thumbnailFileId);
        
        snapshotRecordMapper.insert(record);
        
        log.info("摄像头[{}]抓图成功，记录ID: {}, fileId: {}, thumbnailFileId: {}, 文件: {}",
            cameraId, record.getId(), ossFile.getFileId(), thumbnailFileId, savedFilePath);
        
        // 10. 构建返回结果
        return buildSuccessResult(camera, record);
    }
    
    @Override
    public Map<Long, SnapshotResultVO> executeBatchSnapshot(List<Long> cameraIds) {
        log.info("批量执行抓图，摄像头数量: {}", cameraIds.size());
        
        Map<Long, SnapshotResultVO> results = new HashMap<>();
        
        // 限制批量数量
        int batchSize = Math.min(cameraIds.size(), SnapshotConstants.MAX_BATCH_SNAPSHOT_SIZE);
        List<Long> limitedCameraIds = cameraIds.subList(0, batchSize);
        
        // 并行执行抓图
        List<CompletableFuture<Void>> futures = limitedCameraIds.stream()
            .map(cameraId -> CompletableFuture.runAsync(() -> {
                try {
                    SnapshotResultVO result = executeSnapshot(
                        cameraId,
                        TriggerSourceEnum.SCHEDULE.getCode()
                    );
                    synchronized (results) {
                        results.put(cameraId, result);
                    }
                } catch (Exception e) {
                    log.error("摄像头[{}]批量抓图异常", cameraId, e);
                }
            }, executorService))
            .toList();
        
        // 等待所有任务完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        
        log.info("批量抓图完成，成功: {}, 失败: {}",
            results.values().stream().filter(r -> SnapshotStatusEnum.SUCCESS.getCode().equals(r.getStatus())).count(),
            results.values().stream().filter(r -> SnapshotStatusEnum.FAILED.getCode().equals(r.getStatus())).count()
        );
        
        return results;
    }
    
    @Override
    public SnapshotResultVO manualSnapshot(Long cameraId, String operator) {
        log.info("用户[{}]手动触发摄像头[{}]抓图", operator, cameraId);
        return executeSnapshot(cameraId, TriggerSourceEnum.USER.getCode());
    }
    
    @Override
    public SnapshotResultVO eventSnapshot(Long cameraId, Long alarmId) {
        log.info("告警[{}]触发摄像头[{}]抓图", alarmId, cameraId);
        
        return executeSnapshot(cameraId, TriggerSourceEnum.ALARM.getCode());
    }
    
    // ==================== 私有辅助方法 ====================
    
    /**
     * 构建失败结果
     * 失败时不保存到数据库，只记录日志
     */
    private SnapshotResultVO buildFailedResult(Long cameraId, String triggerSource, String errorMessage, long startTime) {
        
        // 只记录日志，不保存到数据库
        log.error("摄像头[{}]抓图失败 - 触发源: {}, 原因: {}",
            cameraId, triggerSource, errorMessage);
        
        // 构建返回结果
        SnapshotResultVO result = new SnapshotResultVO();
        result.setCameraId(cameraId);
        result.setStatus(SnapshotStatusEnum.FAILED.getCode());
        result.setSnapshotTime(LocalDateTime.now());
        return result;
    }
    
    /**
     * 构建成功结果
     */
    private SnapshotResultVO buildSuccessResult(HwCamera camera, HwCameraSnapshotRecord record) {
        SnapshotResultVO result = new SnapshotResultVO();
        result.setRecordId(record.getId());
        result.setCameraId(camera.getId());
        result.setCameraName(camera.getName());
        result.setStrategyId(record.getStrategyId());
        result.setSnapshotTime(record.getCreateTime()); // Use create_time as snapshotTime
        result.setStatus(SnapshotStatusEnum.SUCCESS.getCode());
        
        // 通过 OSS fileId 获取访问URL
        if (StrUtil.isNotEmpty(record.getFileId())) {
            result.setImageUrl(ossFileService.getOssFileUrl(record.getFileId()));
        }
        if (StrUtil.isNotEmpty(record.getThumbnailFileId())) {
            result.setThumbnailUrl(ossFileService.getOssFileUrl(record.getThumbnailFileId()));
        }
        
        return result;
    }
    
    /**
     * 生成输出文件路径
     * 路径格式: {snapshotRootDir}/{cameraId}/{yyyyMMdd}/{cameraId}_{yyyyMMdd_HHmmss_SSS}.jpg
     */
    private String generateOutputFilePath(Long cameraId, String cameraName) {
        LocalDateTime now = LocalDateTime.now();
        String dateDir = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String fileName = cameraId + "_" + now.format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss_SSS")) + ".jpg";
        
        // 构建完整路径
        String relativePath = Paths.get(snapshotRootDir, cameraId.toString(), dateDir, fileName).toString();
        
        // 确保目录存在
        File outputDir = new File(Paths.get(snapshotRootDir, cameraId.toString(), dateDir).toString());
        if (!outputDir.exists()) {
            outputDir.mkdirs();
        }
        
        return relativePath;
    }
    
    /**
     * 获取可用的流地址
     * 优先级：RTMP > RTSP
     * 
     * @param camera 摄像头对象
     * @return 可用的流地址
     */
    private String getAvailableStreamUrl(HwCamera camera) {
        // 1. 优先尝试构建 RTMP 地址（从配置中动态获取端口和app）
        String rtmpUrl = StreamUrlBuilder.buildRtmpUrl(camera);
        if (StrUtil.isNotBlank(rtmpUrl)) {
            log.info("摄像头[{}]使用 RTMP 地址抓图: {}", camera.getId(), rtmpUrl);
            return rtmpUrl;
        }
        
        // 2. 其次尝试构建 RTSP 地址
        String rtspUrl = StreamUrlBuilder.buildRtspUrl(camera);
        if (StrUtil.isNotBlank(rtspUrl)) {
            log.info("摄像头[{}]使用 RTSP 地址抓图: {}", camera.getId(), rtspUrl);
            return rtspUrl;
        }
        
        // 如果都没有配置，返回 null
        return null;
    }
    
    /**
     * 从摄像头信息中提取 streamId
     * streamId 格式：sipUsername_videoChannel
     * 
     * @param camera 摄像头对象
     * @return streamId，如果无法构建则返回 null
     */
    private String extractStreamId(HwCamera camera) {
        if (StrUtil.isAllNotBlank(camera.getSipUsername(), camera.getVideoChannel())) {
            return camera.getSipUsername() + "_" + camera.getVideoChannel();
        }
        return null;
    }
    
    /**
     * 生成缩略图
     * @param originalImagePath 原图路径
     * @return 缩略图路径，失败返回null
     */
    private String generateThumbnail(String originalImagePath) {
        try {
            // 构建缩略图输出路径
            String thumbnailPath = buildThumbnailPath(originalImagePath);
            
            // 使用 ImageUtils 生成缩略图
            boolean success = ImageUtils.generateThumbnail(
                originalImagePath, 
                thumbnailPath, 
                SnapshotConstants.THUMBNAIL_WIDTH, 
                SnapshotConstants.THUMBNAIL_HEIGHT
            );
            
            if (success) {
                log.debug("缩略图生成成功: 原图={}, 缩略图={}, 尺寸={}x{}", 
                    originalImagePath, thumbnailPath, 
                    SnapshotConstants.THUMBNAIL_WIDTH, SnapshotConstants.THUMBNAIL_HEIGHT);
                return thumbnailPath;
            } else {
                log.warn("缩略图生成失败: 原图={}", originalImagePath);
                return null;
            }
            
        } catch (Exception e) {
            log.error("生成缩略图异常: 原图={}", originalImagePath, e);
            return null;
        }
    }
    
    /**
     * 构建缩略图文件路径
     * @param originalImagePath 原图路径
     * @return 缩略图路径
     */
    private String buildThumbnailPath(String originalImagePath) {
        // 获取原图的目录和文件名
        String directory = cn.hutool.core.io.FileUtil.getParent(originalImagePath, 1);
        String fileName = cn.hutool.core.io.FileUtil.getPrefix(originalImagePath); // 获取不带扩展名的文件名
        
        // 构建缩略图路径：同一目录下，文件名添加 _thumbnail 后缀
        return java.nio.file.Paths.get(directory, fileName + "_thumbnail.jpg").toString();
    }
}

